package org.liang.code.utils;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.encoders.Hex;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;

public class SMUtils {

    // 初始化BC安全管理器
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 生成 SM2 密钥对
     * @return KeyPair
     * @throws NoSuchAlgorithmException 未找到指定算法
     * @throws NoSuchProviderException 未找到安全管理器
     * @throws InvalidAlgorithmParameterException 算法参数无效
     */
    public static KeyPair generateSM2KeyPair() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
        // 获取国密 SM2 椭圆曲线参数
        X9ECParameters ecParameters = GMNamedCurves.getByName("sm2p256v1");
        ECParameterSpec spec = new ECParameterSpec(
                ecParameters.getCurve(), ecParameters.getG(), ecParameters.getN(), ecParameters.getH()
        );
        // 生成密钥对
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
        kpg.initialize(spec, new SecureRandom());
        return kpg.generateKeyPair();
    }

    /**
     * SM2 加密
     * @param publicKey 公钥
     * @param plaintext 明文字节数组
     * @return 密文字节数组
     * @throws Exception 异常
     */
    public static byte[] sm2Encrypt(PublicKey publicKey, byte[] plaintext) throws Exception {
        BCECPublicKey bcPubKey = (BCECPublicKey) publicKey;

        ECParameterSpec ecSpec = bcPubKey.getParameters();
        ECDomainParameters domainParams = new ECDomainParameters(
                ecSpec.getCurve(),
                ecSpec.getG(),
                ecSpec.getN(),
                ecSpec.getH()
        );
        ECPublicKeyParameters pubKeyParams = new ECPublicKeyParameters(
                bcPubKey.getQ(), domainParams);

        SM2Engine engine = new SM2Engine();
        engine.init(true, new ParametersWithRandom(pubKeyParams, new SecureRandom()));
        return engine.processBlock(plaintext, 0, plaintext.length);
    }

    /**
     * SM2 解密
     * @param privateKey 私钥
     * @param ciphertext 密文字节数组
     * @return 明文字节数组
     * @throws Exception 异常
     */
    public static byte[] sm2Decrypt(PrivateKey privateKey, byte[] ciphertext) throws Exception {
        BCECPrivateKey bcPrivKey = (BCECPrivateKey) privateKey;

        ECParameterSpec ecSpec = bcPrivKey.getParameters();
        ECDomainParameters domainParams = new ECDomainParameters(
                ecSpec.getCurve(),
                ecSpec.getG(),
                ecSpec.getN(),
                ecSpec.getH()
        );
        ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters(
                bcPrivKey.getD(), domainParams);

        SM2Engine engine = new SM2Engine();
        engine.init(false, privKeyParams);
        return engine.processBlock(ciphertext, 0, ciphertext.length);
    }

    /**
     * SM3 Hash加密
     * @param inputString 明文字符串
     * @return 加密字符串
     */
    public static String sm3Hash(String inputString) {
        byte[] input = inputString.getBytes(StandardCharsets.UTF_8);
        SM3Digest digest = new SM3Digest();
        digest.update(input, 0, input.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        return Hex.toHexString(hash);
    }

    /**
     * SM4 对称加密
     * @param key 16字节密钥
     * @param iv 16字节IV
     * @param plaintext 明文字节数组
     * @return 密文字节数组
     * @throws Exception 异常
     */
    public static byte[] sm4Encrypt(byte[] key, byte[] iv, byte[] plaintext) throws Exception {
        PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(CBCBlockCipher.newInstance(new SM4Engine()));
        cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));

        byte[] output = new byte[cipher.getOutputSize(plaintext.length)];
        int len = cipher.processBytes(plaintext, 0, plaintext.length, output, 0);
        len += cipher.doFinal(output, len);
        return Arrays.copyOf(output, len);
    }

    /**
     * SM4 对称解密
     * @param key 16字节密钥
     * @param iv 16字节IV
     * @param ciphertext 密文字节数组
     * @return 明文字节数组
     * @throws Exception 异常
     */
    public static byte[] sm4Decrypt(byte[] key, byte[] iv, byte[] ciphertext) throws Exception {
        PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(CBCBlockCipher.newInstance(new SM4Engine()));
        cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));

        byte[] output = new byte[cipher.getOutputSize(ciphertext.length)];
        int len = cipher.processBytes(ciphertext, 0, ciphertext.length, output, 0);
        len += cipher.doFinal(output, len);
        return Arrays.copyOf(output, len);
    }

    /**
     * SM2 签名
     * @param privateKey 私钥
     * @param data 明文字节数组
     * @return 签名字节数组
     * @throws Exception 异常
     */
    public static byte[] sm2Sign(PrivateKey privateKey, byte[] data) throws Exception {
        Signature signature = Signature.getInstance("SM3withSM2", "BC");
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    /**
     * SM2 验签
     * @param publicKey 公钥
     * @param data 明文字节数组
     * @param sig 签名字节数组
     * @return 是否成功
     * @throws Exception 异常
     */
    public static boolean sm2Verify(PublicKey publicKey, byte[] data, byte[] sig) throws Exception {
        Signature signature = Signature.getInstance("SM3withSM2", "BC");
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(sig);
    }

}
